package service;

import entity.Node;
import entity.Relation;
import entity.RelationshipType;
import org.neo4j.driver.Record;
import org.neo4j.driver.Result;
import org.neo4j.driver.Session;
import org.neo4j.driver.Value;
import org.neo4j.driver.types.Relationship;

import java.util.LinkedList;

public class RelationService {
    private final Session session;
    private final NodeService nodeService;

    public RelationService(Session session) {
        this.session = session;
        this.nodeService = new NodeService(this.session);
    }

    private Relation processSingleResult(Result result) {
        if (!result.hasNext()) {
            return null;
        } else {
            Record record = result.next();
            Relationship r = record.get(0).asRelationship();
            Value f = record.get(1);
            Value t = record.get(2);

            Node fromNode = new Node(Node.nodeTypeMap.get(f.asNode().labels().iterator().next()),
                    (String) record.fields().get(1).value().asMap().get("name"),
                    f.asNode().id());
            Node toNode = new Node(Node.nodeTypeMap.get(t.asNode().labels().iterator().next()),
                    (String) record.fields().get(2).value().asMap().get("name"),
                    t.asNode().id());
            return new Relation(r.id(), fromNode, toNode,
                    Relation.relationTypeMap.get(r.type()));
        }
    }

    /**
     * 处理查询结果，其中结果是很多条的
     *
     * @param result 查询结果
     * @return 关系列表
     */
    private LinkedList<Relation> processResult(Result result) {
        LinkedList<Relation> relations = new LinkedList<Relation>();
        while (result.hasNext()) {
            Record record = result.next();
            Relationship r = record.get(0).asRelationship();
            Value f = record.get(1);
            Value t = record.get(2);

            Node fromNode = new Node(Node.nodeTypeMap.get(f.asNode().labels().iterator().next()),
                    (String) record.fields().get(1).value().asMap().get("name"),
                    f.asNode().id());
            Node toNode = new Node(Node.nodeTypeMap.get(t.asNode().labels().iterator().next()),
                    (String) record.fields().get(2).value().asMap().get("name"),
                    t.asNode().id());
            relations.add(new Relation(r.id(), fromNode, toNode,
                    Relation.relationTypeMap.get(r.type())));
        }
        return relations;
    }

    /**
     * 通过起始节点，终止节点的id获取关系
     *
     * @param from 起始节点id
     * @param to   终止节点id
     * @return 关系列表
     */
    public LinkedList<Relation> getRelationshipByFromAndTo(Long from, Long to) {
//        match (f)-[r]->(t) where id(t)=3 AND id(f)=4 return r
        return session.readTransaction(tx -> {
            String query = String.format("match (f)-[r]->(t) where id(t)=%s AND id(f)=%s return r,f,t", to, from);
            Result result = tx.run(query);
            return processResult(result);
        });
    }

    /**
     * 通过起始节点，终止节点的id以及关系的类型获取关系
     *
     * @param from             起始节点的id
     * @param to               终点节点的id
     * @param relationshipType 关系类型
     * @return 符合要求的节点列表
     */
    public LinkedList<Relation> getRelationshipByFromAndToAndType(Long from, Long to,
                                                                  RelationshipType relationshipType) {
//        match (f)-[r]->(t) where id(t)=3 AND id(f)=4 return r
        return session.readTransaction(tx -> {
            String query = String.format("match (f)-[r:%s]->(t) where id(t)=%s AND id(f)=%s return r,f,t",
                    relationshipType.getRelationship(), to, from);
            System.out.println(query);
            Result result = tx.run(query);
            return processResult(result);
        });
    }

    /**
     * 根据节点id获取关系
     *
     * @param id 节点id
     * @return 关系
     */
    public Relation getRelationById(Long id) {
        Result result = session.run(String.format("match (f)-[r]->(t) where id(r)=%s return r,f,t", id));
        return processSingleResult(result);
    }

    /**
     * 获取所有关系
     *
     * @return 关系列表
     */
    public LinkedList<Relation> getRelation() {
        return session.readTransaction(tx -> {
            Result result = tx.run("match (f)-[r]->(t) return r,f,t");
            return processResult(result);
        });
    }

    /**
     * 根据关系的node的label和name 以及relation的type来查询关系 ，node的label和name可以为空
     * ！！！此接口要求较高，不建议使用
     *
     * @param relation
     * @return
     * @deprecated 已经废弃, 因为比较复杂，暂时不用
     */
    public LinkedList<Relation> getRelation(Relation relation) {

        return session.readTransaction(tx -> {
            String query = String.format("MATCH (f:%s)-[r:%S]->(t:%s) WHERE f.name=%s AND t.name=%s AND r return r,f,t",
                    relation.getNodeFrom().getLabel(), relation.getRelationshipType().getRelationship(),
                    relation.getNodeTo().getLabel(), relation.getNodeFrom().getName(), relation.getNodeTo().getName());

            Result result = tx.run(query);
            return processResult(result);
        });
    }

    /**
     * 返回所有以from的id为起点的关系，（只会根据id查询，不会根据name查询）
     *
     * @param from 起点节点
     * @return 关系列表
     */
    public LinkedList<Relation> getRelationByFromNode(Node from) {
        return session.readTransaction(tx -> {
            Result result = tx.run(String.format("match (f)-[r]->(t) where id(f)=%s return r,f,t", from.getId()));
            return processResult(result);
        });
    }

    /**
     * 返回所有以to的id为终点的关系，（只会根据id查询，不会根据name查询）
     *
     * @param to 终点节点
     * @return 关系列表
     */
    public LinkedList<Relation> getRelationByToNode(Node to) {
        return session.readTransaction(tx -> {
            Result result = tx.run(String.format("match (f)-[r]->(t) where id(t)=%s return r,f,t", to.getId()));
            return processResult(result);
        });
    }

    /**
     * 根据id删除关系
     *
     * @param id 关系的id
     * @return 是否成功
     */
    public boolean deleteRelation(Long id) {
        return session.writeTransaction(tx -> {
            tx.run(String.format("MATCH (f)-[r]->(t) WHERE id(r)=%s DELETE r", id));
            return true;
        });
    }

    /**
     * 这个接口只能修改关系的type，不能修改起始结束节点，建议不使用这个方法，update用删除再创建的方式完成
     * !!!不建议使用！！！
     *
     * @param id          关系的id
     * @param newRelation 新的关系的属性
     * @return 新的关系
     */
    public Relation updateRelation(Long id, Relation newRelation) {
        Relation oldRelation = getRelationById(id);
        if (oldRelation == null) {
            return null;
        }
        return session.writeTransaction(tx -> {
            tx.run(String.format("MATCH (f)-[r]->(t) WHERE id(r)=%s REMOVE r:%s SET r:%s RETURN r,f,g", id,
                    oldRelation.getRelationshipType().getRelationship(),
                    newRelation.getRelationshipType().getRelationship()));
            newRelation.setId(id);
            return newRelation;
        });
    }

    /**
     * 创建一个关系
     *
     * @param from              起始节点的id
     * @param to                终点的id
     * @param relationshipType  关系类型
     * @param overwriteRelation 是否覆盖已有的关系，如果是，则from 到 to的所有关系会被删除，然后创建新关系，如果不是，则可能会节点间重复创建关系
     * @return 创建的关系
     * @throws Exception 起始或者终点节点不存在时会抛出异常
     */
    public Relation createRelation(Long from, Long to, RelationshipType relationshipType,
                                   boolean overwriteRelation) throws Exception {

        // 如果不需要创建新节点，则直接创建关系，如果没有查找到节点，则不会创建关系
        // 如果需要创建新节点，就检查是否有节点存在，如果不存在，则创建节点
        Node nodeFrom = nodeService.getNodeById(from);
        Node nodeTo = nodeService.getNodeById(to);
        if (nodeFrom == null) {
            throw new Exception("关系起点节点不存在");
        }
        if (nodeTo == null) {
            throw new Exception("关系终点节点不存在");
        }
        if (overwriteRelation) {
            // 如果需要覆盖关系，则先删除两个节点间的所有，再创建新关系
            LinkedList<Relation> relations = getRelationshipByFromAndToAndType(from, to, relationshipType);
            for (Relation relation : relations) {
                deleteRelation(relation.getId());
            }
            return createRelation(from, to, relationshipType, false);
        }
        return session.writeTransaction(tx -> {
            String query = String.format("match (f), (t) where id(t)=%s AND id(f)=%s create (f)-[r:%s]->(t) return r,f,t",
                    from.toString(), to.toString(), relationshipType.getRelationship());
            System.out.println(query);
            Result result = tx.run(query);
            return processSingleResult(result);
        });

    }

}
